// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_APPCACHE_MOCK_APPCACHE_STORAGE_H_
#define CONTENT_BROWSER_APPCACHE_MOCK_APPCACHE_STORAGE_H_

#include <stdint.h>

#include <deque>
#include <map>
#include <memory>
#include <vector>

#include "base/callback.h"
#include "base/containers/hash_tables.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/appcache/appcache.h"
#include "content/browser/appcache/appcache_disk_cache.h"
#include "content/browser/appcache/appcache_group.h"
#include "content/browser/appcache/appcache_response.h"
#include "content/browser/appcache/appcache_storage.h"

namespace content {
FORWARD_DECLARE_TEST(AppCacheServiceImplTest, DeleteAppCachesForOrigin);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest, BasicFindMainResponse);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest,
    BasicFindMainFallbackResponse);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest, CreateGroup);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest, FindMainResponseExclusions);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest,
    FindMainResponseWithMultipleCandidates);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest, LoadCache_FarHit);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest, LoadGroupAndCache_FarHit);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest, MakeGroupObsolete);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest, StoreNewGroup);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest, StoreExistingGroup);
FORWARD_DECLARE_TEST(MockAppCacheStorageTest,
    StoreExistingGroupExistingCache);
class AppCacheRequestHandlerTest;
class AppCacheServiceImplTest;
class MockAppCacheStorageTest;

// For use in unit tests.
// Note: This class is also being used to bootstrap our development efforts.
// We can get layout tests up and running, and back fill with real storage
// somewhat in parallel.
class MockAppCacheStorage : public AppCacheStorage {
public:
    explicit MockAppCacheStorage(AppCacheServiceImpl* service);
    ~MockAppCacheStorage() override;

    void GetAllInfo(Delegate* delegate) override;
    void LoadCache(int64_t id, Delegate* delegate) override;
    void LoadOrCreateGroup(const GURL& manifest_url, Delegate* delegate) override;
    void StoreGroupAndNewestCache(AppCacheGroup* group,
        AppCache* newest_cache,
        Delegate* delegate) override;
    void FindResponseForMainRequest(const GURL& url,
        const GURL& preferred_manifest_url,
        Delegate* delegate) override;
    void FindResponseForSubRequest(AppCache* cache,
        const GURL& url,
        AppCacheEntry* found_entry,
        AppCacheEntry* found_fallback_entry,
        bool* found_network_namespace) override;
    void MarkEntryAsForeign(const GURL& entry_url, int64_t cache_id) override;
    void MakeGroupObsolete(AppCacheGroup* group,
        Delegate* delegate,
        int response_code) override;
    void StoreEvictionTimes(AppCacheGroup* group) override;
    AppCacheResponseReader* CreateResponseReader(const GURL& manifest_url,
        int64_t response_id) override;
    AppCacheResponseWriter* CreateResponseWriter(
        const GURL& manifest_url) override;
    AppCacheResponseMetadataWriter* CreateResponseMetadataWriter(
        int64_t response_id) override;
    void DoomResponses(const GURL& manifest_url,
        const std::vector<int64_t>& response_ids) override;
    void DeleteResponses(const GURL& manifest_url,
        const std::vector<int64_t>& response_ids) override;

private:
    friend class AppCacheRequestHandlerTest;
    friend class AppCacheServiceImplTest;
    friend class AppCacheUpdateJobTest;
    friend class MockAppCacheStorageTest;

    typedef base::hash_map<int64_t, scoped_refptr<AppCache>> StoredCacheMap;
    typedef std::map<GURL, scoped_refptr<AppCacheGroup>> StoredGroupMap;
    typedef std::set<int64_t> DoomedResponseIds;
    typedef std::map<int64_t, std::pair<base::Time, base::Time>>
        StoredEvictionTimesMap;

    void ProcessGetAllInfo(scoped_refptr<DelegateReference> delegate_ref);
    void ProcessLoadCache(int64_t id,
        scoped_refptr<DelegateReference> delegate_ref);
    void ProcessLoadOrCreateGroup(
        const GURL& manifest_url, scoped_refptr<DelegateReference> delegate_ref);
    void ProcessStoreGroupAndNewestCache(
        scoped_refptr<AppCacheGroup> group, scoped_refptr<AppCache> newest_cache,
        scoped_refptr<DelegateReference> delegate_ref);
    void ProcessMakeGroupObsolete(scoped_refptr<AppCacheGroup> group,
        scoped_refptr<DelegateReference> delegate_ref,
        int response_code);
    void ProcessFindResponseForMainRequest(
        const GURL& url, scoped_refptr<DelegateReference> delegate_ref);

    void ScheduleTask(const base::Closure& task);
    void RunOnePendingTask();

    void AddStoredCache(AppCache* cache);
    void RemoveStoredCache(AppCache* cache);
    void RemoveStoredCaches(const AppCacheGroup::Caches& caches);
    bool IsCacheStored(const AppCache* cache)
    {
        return stored_caches_.find(cache->cache_id()) != stored_caches_.end();
    }

    void AddStoredGroup(AppCacheGroup* group);
    void RemoveStoredGroup(AppCacheGroup* group);
    bool IsGroupStored(const AppCacheGroup* group)
    {
        return IsGroupForManifestStored(group->manifest_url());
    }
    bool IsGroupForManifestStored(const GURL& manifest_url)
    {
        return stored_groups_.find(manifest_url) != stored_groups_.end();
    }

    // These helpers determine when certain operations should complete
    // asynchronously vs synchronously to faithfully mimic, or mock,
    // the behavior of the real implemenation of the AppCacheStorage
    // interface.
    bool ShouldGroupLoadAppearAsync(const AppCacheGroup* group);
    bool ShouldCacheLoadAppearAsync(const AppCache* cache);

    // Lazily constructed in-memory disk cache.
    AppCacheDiskCache* disk_cache()
    {
        if (!disk_cache_) {
            const int kMaxCacheSize = 10 * 1024 * 1024;
            disk_cache_.reset(new AppCacheDiskCache);
            disk_cache_->InitWithMemBackend(kMaxCacheSize, net::CompletionCallback());
        }
        return disk_cache_.get();
    }

    // Simulate failures for testing. Once set all subsequent calls
    // to MakeGroupObsolete or StorageGroupAndNewestCache will fail.
    void SimulateMakeGroupObsoleteFailure()
    {
        simulate_make_group_obsolete_failure_ = true;
    }
    void SimulateStoreGroupAndNewestCacheFailure()
    {
        simulate_store_group_and_newest_cache_failure_ = true;
    }

    // Simulate FindResponseFor results for testing. These
    // provided values will be return on the next call to
    // the corresponding Find method, subsequent calls are
    // unaffected.
    void SimulateFindMainResource(const AppCacheEntry& entry,
        const GURL& fallback_url,
        const AppCacheEntry& fallback_entry,
        int64_t cache_id,
        int64_t group_id,
        const GURL& manifest_url)
    {
        simulate_find_main_resource_ = true;
        simulate_find_sub_resource_ = false;
        simulated_found_entry_ = entry;
        simulated_found_fallback_url_ = fallback_url;
        simulated_found_fallback_entry_ = fallback_entry;
        simulated_found_cache_id_ = cache_id;
        simulated_found_group_id_ = group_id;
        simulated_found_manifest_url_ = manifest_url,
        simulated_found_network_namespace_ = false; // N/A to main resource loads
    }
    void SimulateFindSubResource(
        const AppCacheEntry& entry,
        const AppCacheEntry& fallback_entry,
        bool network_namespace)
    {
        simulate_find_main_resource_ = false;
        simulate_find_sub_resource_ = true;
        simulated_found_entry_ = entry;
        simulated_found_fallback_entry_ = fallback_entry;
        simulated_found_cache_id_ = kAppCacheNoCacheId; // N/A to sub resource loads
        simulated_found_manifest_url_ = GURL(); // N/A to sub resource loads
        simulated_found_group_id_ = 0; // N/A to sub resource loads
        simulated_found_network_namespace_ = network_namespace;
    }

    void SimulateGetAllInfo(AppCacheInfoCollection* info)
    {
        simulated_appcache_info_ = info;
    }

    void SimulateResponseReader(AppCacheResponseReader* reader)
    {
        simulated_reader_.reset(reader);
    }

    StoredCacheMap stored_caches_;
    StoredGroupMap stored_groups_;
    StoredEvictionTimesMap stored_eviction_times_;
    DoomedResponseIds doomed_response_ids_;
    std::unique_ptr<AppCacheDiskCache> disk_cache_;
    std::deque<base::Closure> pending_tasks_;

    bool simulate_make_group_obsolete_failure_;
    bool simulate_store_group_and_newest_cache_failure_;

    bool simulate_find_main_resource_;
    bool simulate_find_sub_resource_;
    AppCacheEntry simulated_found_entry_;
    AppCacheEntry simulated_found_fallback_entry_;
    int64_t simulated_found_cache_id_;
    int64_t simulated_found_group_id_;
    GURL simulated_found_fallback_url_;
    GURL simulated_found_manifest_url_;
    bool simulated_found_network_namespace_;
    scoped_refptr<AppCacheInfoCollection> simulated_appcache_info_;
    std::unique_ptr<AppCacheResponseReader> simulated_reader_;

    base::WeakPtrFactory<MockAppCacheStorage> weak_factory_;

    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest,
        BasicFindMainResponse);
    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest,
        BasicFindMainFallbackResponse);
    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest, CreateGroup);
    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest,
        FindMainResponseExclusions);
    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest,
        FindMainResponseWithMultipleCandidates);
    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest, LoadCache_FarHit);
    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest,
        LoadGroupAndCache_FarHit);
    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest, MakeGroupObsolete);
    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest, StoreNewGroup);
    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest,
        StoreExistingGroup);
    FRIEND_TEST_ALL_PREFIXES(MockAppCacheStorageTest,
        StoreExistingGroupExistingCache);
    FRIEND_TEST_ALL_PREFIXES(AppCacheServiceImplTest,
        DeleteAppCachesForOrigin);

    DISALLOW_COPY_AND_ASSIGN(MockAppCacheStorage);
};

} // namespace content

#endif // CONTENT_BROWSER_APPCACHE_MOCK_APPCACHE_STORAGE_H_
